package com.investmenttechnologyservices.services;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import java.util.ArrayList;
import java.util.Currency;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.Validate;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.FetchOptions;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.KeyFactory;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.datastore.Query.Filter;
import com.google.appengine.api.datastore.Query.FilterOperator;
import com.google.appengine.api.datastore.Query.FilterPredicate;
import com.google.appengine.api.datastore.Transaction;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.inject.Inject;
import com.investmenttechnologyservices.model.Account;
import com.investmenttechnologyservices.model.CashManagementType;
import com.investmenttechnologyservices.model.Instrument;
import com.investmenttechnologyservices.model.InstrumentType;
import com.investmenttechnologyservices.model.Position;
import com.investmenttechnologyservices.model.PriceFormat;
import com.investmenttechnologyservices.model.StageType;
import com.investmenttechnologyservices.model.Symbol;
import com.investmenttechnologyservices.model.Trade;
import com.investmenttechnologyservices.util.DateUtil;
import com.investmenttechnologyservices.util.Util;

public class TradeServiceImpl implements TradeService {
	
	private final DatastoreService ds;
	private final FirmService firmService;
	private final AccountService accountService;
	private final PositionService positionService;
	private final LoginService loginService;
	private final UserService userService;
	private final SymbolService symbolService;
	private final InstrumentService instrumentService;
	private final DatastoreHelper datastoreHelper;

	private static final Logger log = Logger.getLogger(TradeServiceImpl.class.getName());

	private static final String TRADE = "trade";
	private static final Key itsKey = KeyFactory.createKey("dataProvider", "ITS");
	
	private class AccountTagsRequest {
		public Key key;
		public List<String> tags;
	}
	private AccountTagsRequest accountTagsRequest = null; 
	
	@Inject
	TradeServiceImpl(DatastoreService ds, FirmService firmService, AccountService accountService, 
			PositionService positionService, LoginService loginService, UserService userService,
			SymbolService symbolService, InstrumentService instrumentService, DatastoreHelper datastoreHelper) {
		this.ds = ds;
		this.firmService = firmService;
		this.accountService = accountService;
		this.positionService = positionService;
		this.datastoreHelper = datastoreHelper;
		this.loginService = loginService;
		this.symbolService = symbolService;
		this.instrumentService = instrumentService;
		this.userService = userService;
	}

	@Override
	public void findAll(HttpServletRequest req, HttpServletResponse res) throws Exception {
		Key firmKey = loginService.getFirmKeyForCurrentUser();
		checkNotNull(firmKey, "user firm not found");
		Iterable<Entity> accounts = accountService.getAccountListByFirm(firmKey, true);
		Iterator<Entity> iter = accounts.iterator();
		List<Entity> allTrades = new ArrayList<Entity>();
		while (iter.hasNext()) {
			Entity account = iter.next(); 
			List<Entity> trades = getTradesForAccount(account.getKey());
			checkNotNull(trades);
			allTrades.addAll(trades);
		}
		datastoreHelper.writeEntities(res.getWriter(), TRADE, allTrades);
	}

	@Override
	public Map<String,Object> find(HttpServletRequest req, HttpServletResponse res) throws ServletException {
		return datastoreHelper.find(TRADE, req, res);
	}
	
	@Override
	public void create(HttpServletRequest req, HttpServletResponse res) throws ServletException {
		datastoreHelper.create(TRADE, req, res);
	}

	@Override
	public void stageTypePicker(HttpServletRequest req, HttpServletResponse res) throws ServletException {
		Util.enumPicker(StageType.class, req, res);
	}

	@Override
	public List<Entity> getTradesForAccount(Key accountKey) {
		Query tradeQuery = new Query(TRADE).setAncestor(accountKey);
		return ds.prepare(tradeQuery).asList(FetchOptions.Builder.withDefaults());
	}

	@Override
	public List<Key> createNewTransaction(String instrumentId, String symbolId, Long accountId, Double quantity,
		Double price, Date tradeDate, List<String> tags) {
		return createNewTrade(instrumentId, symbolId, accountId, quantity,	price, tradeDate, tags, 
				StageType.TRANSACTION, false);
		
	}

	@Override
	public List<Key> createNewTrade(String instrumentId, String symbolId, Long accountId, Double quantity,
			Double price, Date tradeDate, List<String> tags, StageType stageType) {
		return createNewTrade(instrumentId, symbolId, accountId, quantity, price, tradeDate, tags, 
				stageType, false);
	}

	@Override
	public List<Key> initTrade(String instrumentId, String symbolId, Long accountId, Double quantity,
			Double price, Date tradeDate, List<String> tags, StageType stageType) {
		return createNewTrade(instrumentId, symbolId, accountId, quantity,	price, tradeDate, tags, 
				stageType, true);
		
	}
	
	@Override
	public List<Key> modifyTrade(Key tradeKey, Double quantity, Double price, Date tradeDate, 
			List<String> tags) {
		log.info("[modifyTrade(in): tradeKey="+tradeKey.toString()+
				((quantity==null)?"":", Qty="+quantity.toString())+
				((price==null)?"":", price="+price.toString())+
				((tradeDate==null)?"":", tradeDate="+tradeDate.toString())+
				((tags == null || tags.isEmpty())?"":", tags="+tags.toString()));
		
		List<Key> symbolKeys = new ArrayList<Key>();
		if (accountService.checkUserAndAccount(tradeKey.getParent())) {
			try {
				Trade trade = cloneTrade(new Trade(ds.get(tradeKey)),false, false);
				if (quantity != null)
					trade.setQuantity(quantity);
				if (price != null)
					trade.setQuotePrice(price);
				if (tradeDate != null)
					trade.setTradeDate(tradeDate);
				if (tags != null && !tags.isEmpty())
					trade.setTags(new HashSet<String>(tags));
				symbolKeys = processTrade(trade, true, false);
			}
			catch (EntityNotFoundException ex) {
				ex.printStackTrace();
			}
		} else
			log.severe("Invalid account for user!");
		return symbolKeys;
	}

	@Override
	public List<Key> cancelTrade(Key tradeKey) {

		List<Key> symbolKeys = new ArrayList<Key>();

		if (accountService.checkUserAndAccount(tradeKey.getParent())) {
			try {
				Trade trade = cloneTrade(new Trade(ds.get(tradeKey)),false, false);
				trade.setQuantity(0.0);
				symbolKeys = processTrade(trade, true, false);
			}
			catch (EntityNotFoundException ex) {
				ex.printStackTrace();
			}
		}
		return symbolKeys;
	}

	@Override
	public List<Key> executeOrder(Key tradeKey, Double quantity, Double price, Date tradeDate) {

		List<Key> symbolKeys = new ArrayList<Key>();

		if (accountService.checkUserAndAccount(tradeKey.getParent())) {
			try {
				Trade trade = new Trade(ds.get(tradeKey));
				Trade newTrade = cloneTrade(trade, false, true);
				newTrade.setStageType(StageType.TRANSACTION);
				newTrade.setQuantity(quantity);
				newTrade.setQuotePrice(price);
				newTrade.setTradeDate(tradeDate);

				symbolKeys = processTrade(newTrade, false, false);
			}
			catch (EntityNotFoundException ex) {
				ex.printStackTrace();
			}
		}
		return symbolKeys;
	}
	
	@Override
	public List<Key> createNewCashTrade(Currency currency, Long accountId, Key instrumentTradeKey, Double quantity, 
			Date tradeDate, Date settlementDate, List<String> tags, StageType stageType) {
		/*
		  	Trade(String userId, StageType stageType, Key symbolKey, Key accountKey, Date tradeDate, Date settlementDate,
			Key instrumentTradeKey, Set<String> tags, Double quantity, Boolean initializePosition) {

		 */
		return null;
	}

	private List<Key> createNewTrade(String instrumentId, String symbolId, Long accountId, Double quantity,
		Double price, Date tradeDate, List<String> tags, StageType stageType, Boolean initialize) {
			
		User user = userService.getCurrentUser();
		checkNotNull(user, "User is not logged in.");
		String userId = user.getUserId().toString();

		Key firmKey = loginService.getFirmKeyForCurrentUser();
		checkNotNull(firmKey, "User's firm not found!");
				
		Iterable<Entity> accounts = accountService.getAccountListByFirm(firmKey, true);
		Iterator<Entity> iter = accounts.iterator();
		// get the accountKey for the Trade based on the firmKey
		Key accountKey = null;
		while (iter.hasNext() && accountKey == null) {
			Key key = iter.next().getKey();
			if (key.getId()==accountId) {
				accountKey = key;
			}
		}
		checkNotNull(accountKey,"Account does not belong to firm!");
		
		Key instrumentKey 	= KeyFactory.createKey("instrument", instrumentId);
		Key symbolKey		= KeyFactory.createKey(instrumentKey, "symbol", symbolId);
		//TODO verify symbol/instrument exist
		
		
		Trade trade = new Trade(userId, stageType, symbolKey, accountKey, tradeDate,
				(tags==null)?null:new HashSet<String>(tags), quantity, price, initialize);

		return processTrade(trade, false, initialize);
	}
	

	public void createNewOrder(String instrumentId, String symbolId, Long accountId, Double quantity,
		List<String> tags) {
			
	}

	/**
	 * To create a new trade pass: oldTrade == null
	 *    cancel an old trade pass: newTrade == null
	 *    update an old trade pass: oldTrade & newTrade
	 *    
	 * @param oldTrade
	 * @param trade
	 * @return
	 */
	private List<Key> processTrade(Trade trade, Boolean cancelCorrect, Boolean initialize) {
		log.info("[processTrade(in): newTrade="+trade.toString()+",\ncancel="+cancelCorrect.toString()+"]");
		
		Trade newTrade				= null;
		Date timeStamp 				= new Date();
		Trade foundTrade 			= null;
		Boolean stageFound			= false;
		StageType previousStageType	= null;
		List<Entity> entities 		= new ArrayList<Entity>();
		List<Trade> trades			= new ArrayList<Trade>();
		Double quantityAtStage		= null;
		StageType stageType			= null;
		
		List<Trade> tradesToUpdate	= new ArrayList<Trade>();
		Long tradeId				= trade.getTradeId();
		
		Key mainSymbolKey			= trade.getSymbolKey();
		//prepare return value
		Set<Key> symbolKeys			= new HashSet<Key>();
		symbolKeys.add(mainSymbolKey);
		
		
		// Handle current stage
		if (tradeId==null) {
			checkState(cancelCorrect==false); // cannot cancel a new trade
			trade.setTradeId(firmService.getNextTradeId(trade.getParentKey()));
			trade.setCreationTimestamp(timeStamp);
			trade.setEffectiveTimestamp(timeStamp);
			if (trade.getTradeDate() == null && 
				trade.getStageType() == StageType.TRANSACTION)
				trade.setTradeDate(DateUtil.createDateUTC(timeStamp));
			trade.setSettlementDate(symbolService.calculateSettlementDate(trade.getSymbolKey(),trade.getTradeDate()));			
			if (!initialize)
				updateQuantityPrevious(trade);
			addAccountDefaultTags(trade);
			//TODO add code to check that this trade is the last trade and has not been postdated. 
			// If it has then we may have to update other trades.
			log.info("[processTrade(1): trade]");
			tradesToUpdate.add(trade);
		
		} else {
			// Handle Transactions which can have multiple occurrences. If cancel/correct flag set
			// then need to find match trade to correct. Otherwise it is a new trade.
			trades = allTradesWithTradeId(trade, false, false); // exclude cash & expired
			log.info("[processTrade: found trades="+trades.size()+"]");
			// New transaction (not a correction or a cancellation)
			if (trade.getStageType().equals(StageType.TRANSACTION) &&
				!cancelCorrect) {
				trade.setCreationTimestamp(timeStamp);
				trade.setEffectiveTimestamp(timeStamp);
				if (trade.getTradeDate() == null)
					trade.setTradeDate(DateUtil.createDateUTC(timeStamp));
				trade.setSettlementDate(symbolService.calculateSettlementDate(trade.getSymbolKey(),trade.getTradeDate()));			
				updateQuantityPrevious(trade);
				addAccountDefaultTags(trade);
				quantityAtStage = trade.getQuantity();
				tradesToUpdate.add(trade);
				log.info("[processTrade(2): trade]");
			} else {
				// either an ORDER/SIMULATION or a cancellation
				Iterator<Trade> iter1 = trades.iterator();
				stageFound	= false;
				stageType = trade.getStageType();
				// for transactions - cancel or correction
				// match the effective date to know that it is the correct transaction
				if (stageType.equals(StageType.TRANSACTION)) {
					while (iter1.hasNext() && !stageFound) {
						foundTrade = iter1.next();
						if (foundTrade.getStageType().equals(trade.getStageType()) &&
							foundTrade.getExpirationTimestamp() == null &&
							foundTrade.getEffectiveTimestamp().equals(trade.getEffectiveTimestamp())) {
							stageFound = true;
						}
					}
				} else {
					while (iter1.hasNext() && !stageFound) {
						foundTrade = iter1.next();
						// for transactions - cancel or correction
						// match the effective date to know that it is the correct transaction
						if (foundTrade.getStageType().equals(trade.getStageType()) &&
							foundTrade.getExpirationTimestamp() == null) {
							stageFound = true;
						}
					}
				}
				
				if (stageFound) {
					// Expire original and set the new version as its replacement
					foundTrade.setExpirationTimestamp(timeStamp);
					tradesToUpdate.add(foundTrade);
					log.info("[processTrade(3): foundTrade= expired]");
					// Correction
					if (cancelCorrect && trade.getQuantity()!=0.0) { 
						trade.setCreationTimestamp(timeStamp);
						trade.setEffectiveTimestamp(timeStamp);
						addAccountDefaultTags(trade);
						quantityAtStage = trade.getQuantity() - foundTrade.getQuantity();
						tradesToUpdate.add(trade);
						log.info("[processTrade(4): trade="+trade.toString()+"]");
					}	
				} else {
					// Previous version not found and set to cancel - error
					Validate.isTrue(!cancelCorrect);
					// Otherwise, new record with no previous
					trade.setCreationTimestamp(timeStamp);
					trade.setEffectiveTimestamp(timeStamp);
					updateQuantityPrevious(trade);
					addAccountDefaultTags(trade);
	
					quantityAtStage = trade.getQuantity();
					
					tradesToUpdate.add(trade);
					log.info("[processTrade(5): trade="+trade.toString()+"]");
	
				}
			}
			// Handle previous stage

			// If there is a change in the quantity then handle otherwise leave previous alone 
			log.info("quantityAtStage="+((quantityAtStage==null)?"null":quantityAtStage.toString()));
			if (quantityAtStage != null && 
				quantityAtStage != 0.0) {
				previousStageType = (trade.getStageType().equals(StageType.TRANSACTION))?StageType.ORDER:
					StageType.SIMULATED;
				Iterator<Trade> iter2 = trades.iterator();
				stageFound	= false;
			
				while (iter2.hasNext() && !stageFound) {
					foundTrade = iter2.next();
					if (foundTrade.getStageType().equals(previousStageType) &&
						foundTrade.getExpirationTimestamp() == null) {
						stageFound = true;
					}
				}
				log.info("stageFound(1)="+stageFound.toString());
				
				if (stageFound) {
					foundTrade.setExpirationTimestamp(timeStamp);
					newTrade = cloneTrade(foundTrade, false, false);
					newTrade.setQuantity(foundTrade.getQuantity() - quantityAtStage);
					log.info("[processTrade(6): foundTrade expired.]");
					tradesToUpdate.add(foundTrade);
					tradesToUpdate.add(newTrade);
					log.info("[processTrade(7): newTrade with qty="+newTrade.getQuantity()+"]");
				} else {
					// loop again through trades and look for first expired at previous stage
					Iterator<Trade> iter3 = trades.iterator();
					stageFound	= false;
					while (iter3.hasNext() && !stageFound) {
						// loop but stop with 1st one should be sorted by creation in descending order
						foundTrade = iter3.next();
						if (foundTrade.getStageType().equals(previousStageType)) {
							stageFound = true;
						}
					}
					log.info("stageFound(2)="+stageFound.toString());
					if (stageFound) {
						newTrade = cloneTrade(foundTrade, false, false);
						newTrade.setQuantity(newTrade.getQuantity()-quantityAtStage);
						tradesToUpdate.add(newTrade);
						log.info("[processTrade(8): newTrade with Qty="+newTrade.getQuantity()+"]");
					}
				}
			}
		}	

		tradesToUpdate = processValueAndCash(tradesToUpdate, timeStamp, initialize);
		// commit instrument trades and expired trades (instrument & cash)
		for (Trade processedTrade : tradesToUpdate) {
			if (processedTrade.getExpirationTimestamp()!=null ||
				processedTrade.getSymbolKey().equals(mainSymbolKey))
				entities.add(processedTrade.asEntity());
			if (!processedTrade.getSymbolKey().equals(mainSymbolKey))
				symbolKeys.add(processedTrade.getSymbolKey());
		}
		
		if (!entities.isEmpty()) {
			Transaction tx = ds.beginTransaction();
			ds.put(entities); 
			log.info("[processTrade(9): post-put entities ="+entities.toString()+"]");
//			tx.commit();
			// some trades were not committed in the first pass
			if (entities.size()<tradesToUpdate.size()) {
				// commit cash trades but first ensure that pointer to instrument trade is updated.
				entities = updateInstrumentTradeKeys(tradesToUpdate, mainSymbolKey);
//				tx = ds.beginTransaction();
				ds.put(entities); 
//				tx.commit();
			}
			tx.commit();
		}

		log.info("[processTrade(out): no. trades to update="+tradesToUpdate.size()+", symbols returned="+
				symbolKeys.toString());
		return new ArrayList<Key>(symbolKeys);
	}
	
	private List<Trade> processValueAndCash(List<Trade> trades, Date timeStamp, Boolean initialize){
		log.info("[processCashandValue(in): trades="+trades.toString()+"]");

		Instrument instrument;
		Symbol symbol;
		Trade cashTrade = null;

		Currency prevInstrumentCurrency = null;
 
		Key symbolKey = null;
		Key prevSymbolKey = null;
		Key instrumentKey = null;
		InstrumentType instrumentType = null;
		InstrumentType prevInstrumentType = null;

		Currency cashCurrency = null;

		Key accountKey = null;
		Key prevAccountKey = null;
		Account account = null;
		
		CashManagementType cashManagementType = null;
		CashManagementType prevCashManagementType = null;
		
		List<Trade> allTrades = new ArrayList<Trade>();
		List<Trade> cashTrades = new ArrayList<Trade>();
		Boolean notFound;
		
		Double quantity;

		for (Trade trade : trades) {
			log.info("[processCash(0): trade="+trade.toString()+"]");
			
			if (prevSymbolKey == null ||
				trade.getSymbolKey().equals(prevSymbolKey)) {
				try {
					// from trade symbol: get currency and instrument type of traded instrument
					symbolKey = trade.getSymbolKey();
					symbol = new Symbol(ds.get(symbolKey));
					cashCurrency = symbol.getCurrency();
					prevInstrumentCurrency = cashCurrency;
					instrumentKey = symbolKey.getParent();
					instrument = new Instrument(ds.get(instrumentKey));
					instrumentType = instrument.getInstrumentType();
					prevInstrumentType = instrumentType;
					log.info("[processCash(1): instrumentType="+instrumentType+"]");
				} catch (EntityNotFoundException ex) {
					ex.printStackTrace();
				}
			} else {
				instrumentType = prevInstrumentType;
				cashCurrency = prevInstrumentCurrency;
			}
			
			if (instrumentType != null && 
				!instrumentType.getIsCash()) {

				// Calculate the values. If cash traded then calculate principal, otherwise just fees and taxes
//				if (trade.getStageType() == StageType.TRANSACTION)
				if (trade.getExpirationTimestamp() == null) 
					trade = calculateAmounts(trade, instrumentType.getCashTraded());
				
				if (!initialize &&
					!trade.getNetAmount().equals(0.0)) {

					
					if (trade.getExpirationTimestamp() == null) {
						// Continue to create a cash txn
						accountKey = trade.getCashAccountKey();
						if (accountKey == null)
							accountKey = trade.getKey().getParent();
						if (prevAccountKey == null ||
							!prevAccountKey.equals(accountKey)) {
							try {
								account = new Account(ds.get(accountKey));
								cashManagementType = account.getCashManagementType();
								prevCashManagementType = cashManagementType;
								prevAccountKey = accountKey;
								log.info("[processCash(2): accountKey="+accountKey.toString()+
										", prevCashManagementType="+prevCashManagementType.toString()+"]");
							} catch (EntityNotFoundException ex) {
								ex.printStackTrace();
							}
						} else 
							cashManagementType = prevCashManagementType;
			
						if (cashManagementType != null && 
							(cashManagementType.equals(CashManagementType.BOTH_CASH_AND_INSTRUMENT) ||
							 cashManagementType.equals(CashManagementType.CASH_ONLY))) {
							// check settlement currency
							if (trade.getSettlementCurrency() != null) 
								cashCurrency.equals(trade.getSettlementCurrency());
							
							// get the cash instrument & then symbol for the trade
							// no settlement unless it is a trade
							instrumentKey = instrumentService.getCashInstrument(
								cashCurrency, 
								(trade.getStageType()==StageType.TRANSACTION)?InstrumentType.CASH:
									InstrumentType.CASH_UNST);
							if (instrumentKey != null)
								log.info("[processCash(4): instrumentKey="+instrumentKey.toString()+"]");
							else
								log.info("[processCash(4): instrumentKey not found!]");
							symbolKey = symbolService.getSymbol(instrumentKey, itsKey);
							if (symbolKey == null)
								symbolKey = symbolService.createSymbolITS(instrumentKey, 
										cashCurrency.getCurrencyCode(), 
										itsKey, 
										cashCurrency);
							
							log.info("[processCash(5): symbolKey="+symbolKey.toString()+"]");
							if (symbolKey != null) {
								quantity = -trade.getNetAmount();
								// change the parent key of the trade if using a different cash account
								if (accountKey != null &&
									!accountKey.equals(trade.getParentKey()))
									cashTrade = cloneTrade(trade, accountKey, false, true);
								else
									cashTrade = cloneTrade(trade, false, true);
								cashTrade.setQuantity(quantity);
								cashTrade.setPrincipalAmount(quantity);
								cashTrade.setNetAmount(quantity);
								cashTrade.setQuotePrice(null);
								cashTrade.setSymbolKey(symbolKey);
								cashTrade.setInstrumentTradeKey(trade.getKey());
								updateQuantityPrevious(cashTrade);
							
								cashTrades.add(cashTrade);
								log.info("[processCash(6): cashTrade="+cashTrade.toString());
							}
						}
					} else {
						if (allTrades.isEmpty())
							allTrades = allTradesWithTradeId(trade, true);
						log.info("[processCash(7a): all Trades="+allTrades.toString());
						// TODO throw an error here if there are not trades
						Iterator<Trade> iter1 = allTrades.iterator();
						notFound = true;
						while (iter1.hasNext() && notFound) {
							cashTrade = iter1.next();
							if (cashTrade.getInstrumentTradeKey() != null &&
								cashTrade.getInstrumentTradeKey().equals(trade.getKey())) {
								notFound = false;
								cashTrade.setExpirationTimestamp(timeStamp);
								cashTrades.add(cashTrade);
							}
						}
						log.info("[processCash(7b): notFound="+notFound+", cashTrade="+
								cashTrade.toString());

					}
				}
			} 
		}
		trades.addAll(cashTrades);
		log.info("[processCashandValue(out): no. trades="+trades.size()+"]");
		return trades;
		
	}

	private Trade calculateAmounts(Trade trade, Boolean cashTraded){
		log.info("[calculateAmounts(in): trades="+trade.toString()+", cashTrade="+cashTraded+"]");

		Key prevSymbolKey = null;
		
		PriceFormat priceFormat = null;
		Double unitPrice = 0.0;

		if (cashTraded) {
			if (priceFormat == null ||
				trade.getSymbolKey() != prevSymbolKey) {
				// from trade symbol: get currency and instrument type of traded instrument
				priceFormat = symbolService.getPriceFormat(trade.getSymbolKey());
				prevSymbolKey = trade.getSymbolKey();
				log.info("[calculateAmounts(1): priceFormat="+priceFormat.toString());
			}
			unitPrice = trade.getQuotePrice();
			if (priceFormat != null && 
				priceFormat != PriceFormat.SIMPLE) {
				unitPrice /= 100.0;
//				trade.setDecimalPrice(unitPrice);
			}
			trade.setPrincipalAmount(Math.round(unitPrice*trade.getQuantity()*100.0)/100.0);
		} else 
			trade.setPrincipalAmount(0.0);
		trade.setNetAmount(trade.getPrincipalAmount()+trade.getCommissionAmount()+
				trade.getTaxAmount()+trade.getFeesAmount());

		log.info("[calculateAmounts(out): trade="+trade.toString()+"]");
		return trade;
	}


	private List<Entity> updateInstrumentTradeKeys(List<Trade> trades, Key symbolKey) {
		//entities = allTradesWithTradeId(trade, true, false);
		
		log.info("[updateInstrumentTradeKeys(in): symbolKey="+symbolKey.toString()+"]");
		List<Entity> cashTrades = new ArrayList<Entity>();

		for (Trade cashTrade : trades) {
			log.info("cashTrade="+cashTrade.toString());
			if (cashTrade.getExpirationTimestamp()==null &&
				!cashTrade.getSymbolKey().equals(symbolKey)) {
				if (cashTrade.getInstrumentTradeKey()==null) {
					log.info("looking for instrument trade.");
					for (Trade trade : trades) {
						log.info("Checking "+trade.toString());
						if (trade.getExpirationTimestamp()==null &&
							trade.getSymbolKey().equals(symbolKey) &&
							trade.getNetAmount().equals(-cashTrade.getNetAmount())){
							cashTrade.setInstrumentTradeKey(trade.getKey());
							cashTrades.add(cashTrade.asEntity());
							break;
						}
					}
				} else {
					log.info("Already known key: "+cashTrade.getInstrumentTradeKey());
					cashTrades.add(cashTrade.asEntity());
				}
			}
		}
		
		log.info("[updateInstrumentTradeKeys(out): cashTrades="+cashTrades.toString()+"]");
		return cashTrades;
	}
	/*
	private List<Entity> previousStageTrades(Trade trade){

		
		Long tradeId 		= trade.getTradeId();
		StageType stageType = trade.getStageType();

		List<Entity> result = new ArrayList<Entity>();
		
		Filter tradeStageTypeFilter = null;
		switch (stageType) {
		case TRANSACTION:
			tradeStageTypeFilter = CompositeFilterOperator.or(
				new FilterPredicate("stageType", FilterOperator.EQUAL,StageType.EXECUTION.name()),
				new FilterPredicate("stageType", FilterOperator.EQUAL,StageType.ORDER.name()));
			break;
		case EXECUTION:
			tradeStageTypeFilter = 
				new FilterPredicate("stageType", FilterOperator.EQUAL,StageType.ORDER.name());
			break;
		case ORDER:
			tradeStageTypeFilter = 
				new FilterPredicate("stageType", FilterOperator.EQUAL,StageType.SIMULATED.name());
			break;
		default:
			// Only process executions and orders
			return result;  // return empty list
		}
		
		Filter tradeIdFilter = 
			new FilterPredicate("tradeId",FilterOperator.EQUAL,tradeId);
//		Filter tradeExpirationFilter = 
//				new FilterPredicate("expirationTimestamp",FilterOperator.EQUAL,null);
//		Filter notActiveFilter =
//			CompositeFilterOperator.and(tradeIdFilter, tradeStageTypeFilter, tradeExpirationFilter);
		Filter notActiveFilter =
				CompositeFilterOperator.and(tradeIdFilter, tradeStageTypeFilter);
		
		Query query = 
			new Query ("trade").setFilter(notActiveFilter)
				.setAncestor(trade.getParentKey())
				.addSort("creationTimestamp", SortDirection.DESCENDING);

		FetchOptions fo = FetchOptions.Builder.withDefaults();
		result = ds.prepare(query).asList(fo);

		return result;
	}
*/
	private List<Trade> allTradesWithTradeId(Trade trade, Boolean includeCash){
		return allTradesWithTradeId(trade, includeCash, true);
	}
	
	private List<Trade> allTradesWithTradeId(Trade trade, Boolean includeCash, 
			Boolean includeExpired){
		log.info("[allTradesWithTradeId(in): trade="+trade.toString()+", includeCash="+
			includeCash.toString()+", includeExpired="+includeExpired.toString()+"]");
		
		Long tradeId 		= trade.getTradeId();
		Key accountKey		= trade.getParentKey();

		Filter tradeFilter = 
				new FilterPredicate("tradeId",FilterOperator.EQUAL,tradeId);
/*
		Filter compositeFilter;
		Filter cashFilter = 
				new FilterPredicate("instrumentTradeKey",FilterOperator.EQUAL,null);
		Filter expiredFilter = 
				new FilterPredicate("expirationTimestamp",FilterOperator.NOT_EQUAL,null);
		
		if (includeCash && includeExpired)
			compositeFilter = tradeFilter;
		else if (includeCash && !includeExpired)
			compositeFilter = CompositeFilterOperator.and(expiredFilter, tradeFilter);
		else if (!includeCash && includeExpired)
			compositeFilter = CompositeFilterOperator.and(tradeFilter, cashFilter);
		else
			compositeFilter = CompositeFilterOperator.and(expiredFilter, tradeFilter, cashFilter);
*/
		
		Query query = 
			new Query("trade").setFilter(tradeFilter)
				.setAncestor(accountKey);

		FetchOptions fo = FetchOptions.Builder.withDefaults();
		List<Entity> entities = ds.prepare(query).asList(fo);
		log.info("[allTradesWithTradeId(1): found="+entities.size()+"\n\nentities="+entities.toString()+"]");
		
		List<Trade> trades = new ArrayList<Trade>();
		
		for (Entity entity : entities) {
			trade = new Trade(entity);
			if (includeCash && includeExpired)
				trades.add(trade);
			else if(includeCash && trade.getExpirationTimestamp()==null)
				trades.add(trade);
			else if(includeExpired && trade.getInstrumentTradeKey()==null)
				trades.add(trade);
			else if(trade.getExpirationTimestamp()==null &&
					trade.getInstrumentTradeKey()==null)
				trades.add(trade);
		}
		
		return trades;
	}


	private void updateQuantityPrevious(Trade trade) {
		log.info("[updateQuantityPrevious(in): trade in]");
		Date asOfDate = trade.getEffectiveTimestamp();
		Key accountKey = trade.getParentKey();
		List<Key> symbolKeys = new ArrayList<Key>();
		symbolKeys.add(trade.getSymbolKey());
				
		List<Position> positions = positionService.getSymbolPositionsAsOf(asOfDate, accountKey, 
				symbolKeys, true);
		
		if (!positions.isEmpty()) {
			trade.setQuantityPrevious(positions.get(0).getQuantity());
			log.info("[updateQuantityPrevious(out): updated QP="+trade.getQuantityPrevious()+"]");
		} else {
			trade.setQuantityPrevious(0.0);
		}
		return;
	}

	
	public Trade cloneTrade(Trade trade, Boolean reverse, Boolean zero) {
		return cloneTrade(trade, null, reverse, zero);
	}
	
	
	public Trade cloneTrade(Trade trade, Key accountKey, Boolean reverse, Boolean zero) {
		// TODO need to copy objects and change the key
		Trade tradeClone; 
		if (accountKey == null)
			tradeClone = new Trade(trade.getParentKey());
		else
			tradeClone = new Trade(accountKey);
		tradeClone.setTradeId(trade.getTradeId());
		tradeClone.setStageType(trade.getStageType());
		tradeClone.setPositionType(trade.getPositionType());
		tradeClone.setSymbolKey(trade.getSymbolKey());
		tradeClone.setBlockTradeKey(trade.getBlockTradeKey());
		tradeClone.setTradeDate(trade.getTradeDate());
		tradeClone.setSettlementDate(trade.getSettlementDate());
		tradeClone.setBrokerKey(trade.getBrokerKey());
		tradeClone.setQuantityPrevious(trade.getQuantityPrevious());
		tradeClone.setQuotePrice(trade.getQuotePrice());
		tradeClone.setCashAccountKey(trade.getCashAccountKey());
		tradeClone.setDecimalPrice(trade.getDecimalPrice());
		tradeClone.setFxRateInstPf(trade.getFxRateInstPf());
		tradeClone.setFxRateInstSett(trade.getFxRateInstSett());
		// TODO add all fields
		
		tradeClone.setEffectiveTimestamp(trade.getEffectiveTimestamp());

		if (reverse) {
			tradeClone.setQuantity(-trade.getQuantity());
			tradeClone.setNetAmount(-trade.getNetAmount());
			tradeClone.setPrincipalAmount(-trade.getPrincipalAmount());
			tradeClone.setAccuredInterestAmount(-trade.getAccuredInterestAmount());
			tradeClone.setCommissionAmount(-trade.getCommissionAmount());
			tradeClone.setTaxAmount(-trade.getTaxAmount());
			tradeClone.setFeesAmount(-trade.getFeesAmount());
		} else {
			tradeClone.setQuantity(trade.getQuantity());
			tradeClone.setNetAmount(trade.getNetAmount());
			tradeClone.setPrincipalAmount(trade.getPrincipalAmount());
			tradeClone.setAccuredInterestAmount(trade.getAccuredInterestAmount());
			tradeClone.setCommissionAmount(trade.getCommissionAmount());
			tradeClone.setTaxAmount(trade.getTaxAmount());
			tradeClone.setFeesAmount(trade.getFeesAmount());
		}
		
		if (zero) {
			tradeClone.setSettlementDate(null);
			tradeClone.setQuotePrice(null);
			tradeClone.setAccuredInterestAmount(null);
			tradeClone.setNetAmount(null);
			tradeClone.setPrincipalAmount(null);
			tradeClone.setFeesAmount(null);
			tradeClone.setTaxAmount(null);
			tradeClone.setCommissionAmount(null);
		}

		return tradeClone;
	}
	
	private void addAccountDefaultTags(Trade trade) {
		log.info("addAccountDefaultTags(in): trade.key="+trade.getKey().toString()+
			"]");
		
 		Key key = trade.getKey().getParent();
 		Account account;
 		List<String> defaultTags 	= null;
 		List<String> tags 			= null;
 		Set<String> newTags 		= new HashSet<String>();

 		if (requestRepeated(key)) {
			log.info("[returning cached result: tags="+accountTagsRequest.tags+"]");
			defaultTags = accountTagsRequest.tags;
		} else {
			try {
 	 			account = new Account(ds.get(key));
 				defaultTags = account.getDefaultTags();
 				setAccountDefaultTags(key, defaultTags);
			} catch (EntityNotFoundException ex) {
				// TODO Auto-generated catch block
				ex.printStackTrace();
			}
		}
 		
 		// put the default tags first. Don't update if no defaults.
 		if (!defaultTags.isEmpty()) {
 			newTags.addAll(defaultTags);
 			tags = trade.getTags();
 			if (tags != null && !tags.isEmpty()) {
	 			for (String tag : tags) {
					newTags.add(tag);
				}
 			}
 	 		trade.setTags(newTags);
 			log.info("addAccountDefaultTags(out): tags="+trade.getTags().toString());
		}
  		return;
		
	}
	
	private void setAccountDefaultTags(Key key, List<String> tags) {
		if (accountTagsRequest == null) {
			accountTagsRequest = new AccountTagsRequest();
		}
		accountTagsRequest.key = key;
		accountTagsRequest.tags = tags;
	}

	private Boolean requestRepeated(Key key) {
		if (accountTagsRequest != null &&
			key 	== accountTagsRequest.key) {
			return true;
		} else {
			return false;
		}
	}

}
